/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <cpu/cpu.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "memory/mem.h"
#include "tracer/mtracer.h"
#include "tracer/itracer.h"
#include "sdb.h"

void init_regex();
void init_wp_pool();
bool batch_mode = false;
/* We use the `readline' library to provide more flexibility to read from stdin. */
static char* rl_gets() {
  static char *line_read = NULL;

  if (line_read) {
    free(line_read);
    line_read = NULL;
  }

  line_read = readline("(npc) ");

  if (line_read && *line_read) {
    add_history(line_read);
  }

  return line_read;
}

static int cmd_c(char *args) {
  continue_flag = true;
  silent = true;
  while (continue_flag&& !sim_finish)
  {
    cpu_exec_once();
  }
  if(batch_mode) return -1;
  return 0;
}


static int cmd_q(char *args) {
  
  return -1;
}

static int cmd_help(char *args);
static int cmd_si(char *args);
static int cmd_info(char *args);
static int cmd_x(char *args);
static int cmd_p(char *args);
static int cmd_w(char *args);
static int cmd_d(char *args);

static struct {
  const char *name;
  const char *description;
  int (*handler) (char *);
} cmd_table [] = {
  { "help", "Display information about all supported commands", cmd_help },
  { "c", "Continue the execution of the program", cmd_c },
  { "q", "Exit NEMU", cmd_q },
  { "si","Step execute",cmd_si},
  { "info","print reg state or watch point or ring buff",cmd_info},
  { "x", "Scan memory by expr",cmd_x},
  { "p", "find the value of expression",cmd_p},
  { "w", "set watchpoint",cmd_w},
  { "d", "delete watchpoint",cmd_d},
  /* TODO: Add more commands */

};


static int cmd_x(char *args) {
  char *arg1 = strtok(NULL, " ");
  char *arg2 = strtok(NULL, " ");
  if(arg1 == NULL || arg2 == NULL) return 0;
  int n = atoi(arg1);
  bool success = false;
  word_t addr = expr(arg2,&success);
  if( success == false) {
    printf("expr is not correct.\n");
    return 0;
  }
  printf("scan memory start addr : 0x%lx\n",addr);
  for (int i = 0; i < n; ++i) {
    uint64_t val = *(uint64_t*)guest_to_host(addr+4*i);
    printf("0x%016lx \t",val);
  }
  printf("\n");
  return 0;
}
static int cmd_p(char *args) {
  char *arg = strtok(NULL, " ");
  if(arg == NULL) return 0;
  bool success = false;
  word_t value = expr(arg,&success);
  if(success == false) return 0;
  printf("expr = %ld\n",value);
  printf("hex is 0x%lx\n",value);
  return 0;
}
static int cmd_w(char *args) {
  char *arg = strtok(NULL, " ");
  if(arg == NULL) return 0;
  // bool success = false;
  // word_t val = expr(arg,&success);
  // if(success == false) return 0;
  int set_wp(char *str,int len);
  int ret_val = set_wp(arg,strlen(arg));
  if(ret_val == -1) printf("set failed\n");
  else printf("success NO is %d\n",ret_val);
  return 0;
}
static int cmd_d(char *args) {
  char *arg = strtok(NULL, " ");
  if(arg == NULL) return 0;
  int val = atol(arg);
  int del_wp(int NO);
  if(del_wp(val) == -1) printf("del failed\n");
  else printf("del success\n");
  return 0;
}


#define NR_CMD ARRLEN(cmd_table)
static int cmd_si(char *args) {
  char *arg = strtok(NULL, " ");
  int n = 1;
  if(arg != NULL) n = atoi(arg);
  silent = false;
  continue_flag = true;
  while (n--&&continue_flag&&!sim_finish)
  {
    cpu_exec_once();
  }
  
  return 0;
}

static int cmd_info(char *args) {
  char *arg = strtok(NULL, " ");
  if(arg == NULL) {
    goto print_cmd_info_help;
  }
  if(arg[0] == 'r') {
    printf("PC is 0x%lx\n",cpu_state.pc);
    isa_reg_display(&cpu_state);
  }
  else if (arg[0] == 'w')
  {
    void print_all_wp();
    print_all_wp();
  }
  else if (arg[0] == 'i')
  {
    IFONE(CONFIG_ITRACE, itracer.print());
  }
  else if (arg[0] == 'm')
  {
    IFONE(CONFIG_MTRACE, mtracer.print());
  }
  else
  {
    print_cmd_info_help:
    printf("cmd 'info r' to show reg info\n");
    printf("cmd 'info w' to show watchpoint\n");
    printf("cmd 'info i' to show inst trace info\n");
    printf("cmd 'info m' to show memory trace info\n");
        
  } 
   
  return 0;
}

static int cmd_help(char *args) {
  /* extract the first argument */
  char *arg = strtok(NULL, " ");
  int i;

  if (arg == NULL) {
    /* no argument given */
    for (i = 0; i < NR_CMD; i ++) {
      printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description);
    }
  }
  else {
    for (i = 0; i < NR_CMD; i ++) {
      if (strcmp(arg, cmd_table[i].name) == 0) {
        printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description);
        return 0;
      }
    }
    printf("Unknown command '%s'\n", arg);
  }
  return 0;
}

void sdb_set_batch_mode() {
  continue_flag = true;
  // silent = true;
  batch_mode = true;
}

void sdb_mainloop() {
  if (batch_mode) {
    cmd_c(NULL);
    return;
  }

  for (char *str; (str = rl_gets()) != NULL; ) {
    char *str_end = str + strlen(str);

    /* extract the first token as the command */
    char *cmd = strtok(str, " ");
    if (cmd == NULL) { continue; }

    /* treat the remaining string as the arguments,
     * which may need further parsing
     */
    char *args = cmd + strlen(cmd) + 1;
    if (args >= str_end) {
      args = NULL;
    }

#ifdef CONFIG_DEVICE
    extern void sdl_clear_event_queue();
    sdl_clear_event_queue();
#endif

    int i;
    for (i = 0; i < NR_CMD; i ++) {
      if (strcmp(cmd, cmd_table[i].name) == 0) {
        if (cmd_table[i].handler(args) < 0) { return; }
        break;
      }
    }

    if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); }
  }
}

void init_sdb() {
  /* Compile the regular expressions. */
  init_regex();

  /* Initialize the watchpoint pool. */
  init_wp_pool();
}
